﻿// -----------------------------------------------------------------------
// <copyright file="RemixExpressionVisitor.cs" company="Spardo, LLC">
// Copyright (c) 2011 SPardo, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a 
// copy of this software and associated documentation files (the "Software"), 
// to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the 
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included 
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
// </copyright>
// -----------------------------------------------------------------------

namespace BBYOpen.Linq
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Runtime.Serialization;
    using System.Text;
    using System.Web;
    using BBYOpen.Linq.Common;

    internal class RemixExpressionVisitor : ExpressionVisitor
    {
        StringWriter Get { get; set; }
        StringWriter Where { get; set; }
        StringWriter OrderBy { get; set; }
        StringWriter OrderByDescending { get; set; }
        StringWriter Page { get; set; }
        StringWriter Search { get; set; }

        StringWriter Noise { get; set; }

        Stack<StringWriter> Stack = new Stack<StringWriter>();

        StringWriter Current
        {
            get
            {
                return this.Stack.Peek();
            }
        }

        public RemixExpressionVisitor(StringWriter where
            , StringWriter orderBy
            , StringWriter orderByDescending
            , StringWriter page
            , StringWriter search
            , StringWriter get)
        {
            this.Where = where;
            this.OrderBy = orderBy;
            this.OrderByDescending = orderByDescending;
            this.Page = page;
            this.Search = search;
            this.Get = get;

            this.Noise = new StringWriter(new StringBuilder());
        }

        internal static TResult Execute<TResult>(string apiKey, Expression expression)
        {
            TResult output = default(TResult);

            var getBuilder = new StringBuilder();
            var whereBuilder = new StringBuilder();
            var orderByBuilder = new StringBuilder();
            var orderByDescendingBuilder = new StringBuilder();
            var pageBuilder = new StringBuilder();
            var searchBuilder = new StringBuilder();

            var visitor = new RemixExpressionVisitor(new StringWriter(whereBuilder)
                , new StringWriter(orderByBuilder)
                , new StringWriter(orderByDescendingBuilder)
                , new StringWriter(pageBuilder)
                , new StringWriter(searchBuilder)
                , new StringWriter(getBuilder));
            visitor.Visit(expression);

            var getString = getBuilder.ToString();

            string url = string.Empty;
            var name = TypeSystem.GetElementDataContractName(typeof(TResult));

            if (string.IsNullOrEmpty(getString))
            {
                whereBuilder.Append(searchBuilder.ToString());

                var whereString = whereBuilder.ToString().TrimStart("&".ToCharArray());
                var orderByString = orderByBuilder.ToString();

                if (string.IsNullOrEmpty(orderByString) && !string.IsNullOrEmpty(orderByDescendingBuilder.ToString()))
                    orderByString = string.Format("{0}.desc", orderByDescendingBuilder.ToString());
                
                url = string.Format("http://api.remix.bestbuy.com/v1/{0}{1}?apiKey={2}&format=json{3}{4}"
                    , name
                    , (string.IsNullOrEmpty(whereString)) ? string.Empty : string.Format("({0})", whereString)
                    , apiKey
                    , (string.IsNullOrEmpty(orderByString)) ? string.Empty : string.Format("&sort={0}", orderByString)
                    , pageBuilder.ToString());
            }
            else
            {
                url = string.Format("http://api.remix.bestbuy.com/v1/{0}/{1}.json?apiKey={2}"
                    , name
                    , getString
                    , apiKey);
            }
            url.ToDebug();

            return RemixApi.ApiCall<TResult>(url);
        }

        protected override Expression VisitBinary(BinaryExpression node)
        {
            this.Current.Write("&");
            this.Visit(node.Left);
            switch (node.NodeType)
            {
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                    break;
                //case ExpressionType.Or:
                //    this.Current.Write(" OR ");
                //    break;
                case ExpressionType.Equal:
                    this.Current.Write("=");
                    break;
                case ExpressionType.NotEqual:
                    this.Current.Write("!=");
                    break;
                case ExpressionType.LessThan:
                    this.Current.Write("<");
                    break;
                case ExpressionType.LessThanOrEqual:
                    this.Current.Write("<=");
                    break;
                case ExpressionType.GreaterThan:
                    this.Current.Write(">");
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    this.Current.Write(">=");
                    break;
                default:
                    throw new NotSupportedException(string.Format("The binary operator '{0}' is not supported", node.NodeType));
            }
            this.Visit(node.Right);
            return node;
        }

        protected override Expression VisitBlock(BlockExpression node)
        {
            return base.VisitBlock(node);
        }

        protected override CatchBlock VisitCatchBlock(CatchBlock node)
        {
            return base.VisitCatchBlock(node);
        }

        protected override Expression VisitConditional(ConditionalExpression node)
        {
            return base.VisitConditional(node);
        }

        protected override Expression VisitConstant(ConstantExpression node)
        {
            if (node.Type.Name != "RemixQueryable`1")
            {
                if (node.Type == typeof(string))
                {
                    var constant = Expression.Lambda<Func<object>>(node).Compile();
                    var value = constant();
                    var output = HttpUtility.UrlEncode(value.ToString());
                    if (value is string)
                        output = string.Format("'{0}'", output);

                    this.Current.Write(output);
                }
                else
                {
                    this.Current.Write(node.Value);
                }
            }

            return base.VisitConstant(node);
        }

        protected override Expression VisitDebugInfo(DebugInfoExpression node)
        {
            return base.VisitDebugInfo(node);
        }

        protected override Expression VisitDefault(DefaultExpression node)
        {
            return base.VisitDefault(node);
        }

        protected override Expression VisitDynamic(DynamicExpression node)
        {
            return base.VisitDynamic(node);
        }

        protected override ElementInit VisitElementInit(ElementInit node)
        {
            return base.VisitElementInit(node);
        }

        protected override Expression VisitExtension(Expression node)
        {
            return base.VisitExtension(node);
        }

        protected override Expression VisitGoto(GotoExpression node)
        {
            return base.VisitGoto(node);
        }

        protected override Expression VisitIndex(IndexExpression node)
        {
            return base.VisitIndex(node);
        }

        protected override Expression VisitInvocation(InvocationExpression node)
        {
            return base.VisitInvocation(node);
        }

        protected override Expression VisitLabel(LabelExpression node)
        {
            return base.VisitLabel(node);
        }

        protected override LabelTarget VisitLabelTarget(LabelTarget node)
        {
            return base.VisitLabelTarget(node);
        }

        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            return base.VisitLambda<T>(node);
        }

        protected override Expression VisitListInit(ListInitExpression node)
        {
            return base.VisitListInit(node);
        }

        protected override Expression VisitLoop(LoopExpression node)
        {
            return base.VisitLoop(node);
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Expression.NodeType == ExpressionType.Parameter)
            {
                var output = string.Empty;

                var attrs = node.Member.GetCustomAttributes(typeof(DataMemberAttribute), true);
                var member = attrs.First();

                if (member != null)
                    output = ((DataMemberAttribute)member).Name;
                else
                    output = node.Member.Name;

                this.Current.Write(output);
            }
            else
            {
                var member = Expression.Lambda<Func<object>>(node).Compile();
                this.Current.Write(member());
            }

            return base.VisitMember(node);
        }

        protected override MemberAssignment VisitMemberAssignment(MemberAssignment node)
        {
            return base.VisitMemberAssignment(node);
        }

        protected override MemberBinding VisitMemberBinding(MemberBinding node)
        {
            return base.VisitMemberBinding(node);
        }

        protected override Expression VisitMemberInit(MemberInitExpression node)
        {
            return base.VisitMemberInit(node);
        }

        protected override MemberListBinding VisitMemberListBinding(MemberListBinding node)
        {
            return base.VisitMemberListBinding(node);
        }

        protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
        {
            return base.VisitMemberMemberBinding(node);
        }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            Expression output = node;

            if (node.Method.DeclaringType == typeof(Queryable) ||
                node.Method.DeclaringType == typeof(Extensions))
            {
                switch (node.Method.Name)
                {
                    case "Where":
                        Stack.Push(this.Where);
                        output = base.VisitMethodCall(node);
                        break;
                    case "OrderBy":
                        Stack.Push(this.OrderBy);
                        output = base.VisitMethodCall(node);
                        break;
                    case "OrderByDescending":
                        Stack.Push(this.OrderByDescending);
                        output = base.VisitMethodCall(node);
                        break;
                    case "Page":
                        Stack.Push(this.Page);
                        this.Current.Write("&page=");
                        output = base.VisitMethodCall(node);
                        break;
                    case "Search":
                        Stack.Push(this.Search);
                        this.Current.Write("&search=");
                        output = base.VisitMethodCall(node);
                        break;
                    case "Area":
                        Stack.Push(this.Where);
                        this.Current.Write("&area(");
                        output = base.VisitMethodCall(node);
                        this.Current.Write(")");
                        break;
                    case "Get":
                        Stack.Push(this.Get);
                        output = base.VisitMethodCall(node);
                        break;
                    default:
                        Stack.Push(this.Noise);
                        output = base.VisitMethodCall(node);
                        break;
                }

                Stack.Pop();
            }
            else
            {
                switch (node.Method.Name)
                {
                    case "Contains":
                        this.Current.Write("&");
                        this.Visit(node.Arguments[1]);
                        this.Current.Write(" in(");
                        this.Visit(node.Arguments[0]);
                        this.Current.Write(")");
                        break;
                    default:
                        Stack.Push(this.Noise);
                        output = base.VisitMethodCall(node);
                        Stack.Pop();
                        break;
                }
            }

            return output;
        }

        protected override Expression VisitNew(NewExpression node)
        {
            return base.VisitNew(node);
        }

        protected override Expression VisitNewArray(NewArrayExpression node)
        {
            for (int i = 0; i < node.Expressions.Count(); i++)
            {
                if (i != 0)
                    this.Current.Write(",");

                this.Visit(node.Expressions[i]);
            }

            return node;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return base.VisitParameter(node);
        }

        protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)
        {
            return base.VisitRuntimeVariables(node);
        }

        protected override Expression VisitSwitch(SwitchExpression node)
        {
            return base.VisitSwitch(node);
        }

        protected override SwitchCase VisitSwitchCase(SwitchCase node)
        {
            return base.VisitSwitchCase(node);
        }

        protected override Expression VisitTry(TryExpression node)
        {
            return base.VisitTry(node);
        }

        protected override Expression VisitTypeBinary(TypeBinaryExpression node)
        {
            return base.VisitTypeBinary(node);
        }

        protected override Expression VisitUnary(UnaryExpression node)
        {
            return base.VisitUnary(node);
        }
    }
}
